package com.uinnova.product.eam.service.impl;

import com.alibaba.fastjson.JSON;
import com.binary.core.exception.BinaryException;
import com.binary.jdbc.Page;
import com.uinnova.product.eam.base.util.EamUtil;
import com.uinnova.product.eam.constant.RotaState;
import com.uinnova.product.eam.constant.RoteStartType;
import com.uinnova.product.eam.dao.ArchReviewRotaConfDao;
import com.uinnova.product.eam.dao.ArchReviewRotaDetailConfDao;
import com.uinnova.product.eam.dto.SimpUserInfo;
import com.uinnova.product.eam.local.ArchReviewRotaContext;
import com.uinnova.product.eam.local.ArchReviewRotaContextValue;
import com.uinnova.product.eam.model.es.ArchReviewRotaConf;
import com.uinnova.product.eam.model.es.ArchReviewRotaDetailConf;
import com.uinnova.product.eam.service.ArchReviewRotaConfService;
import com.uinnova.product.eam.vo.*;
import com.uinnova.project.base.diagram.util.RedisUtil;
import com.uino.api.client.permission.IUserApiSvc;
import com.uino.bean.permission.base.SysUser;
import com.uino.bean.permission.query.CSysUser;
import com.uino.dao.permission.ESUserSvc;
import com.uino.dao.util.ESUtil;
import com.uino.util.sys.SysUtil;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.index.query.TermsQueryBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Log4j2
public class ArchReviewRotaConfServiceImpl implements ArchReviewRotaConfService {

    private static final SimpleDateFormat NUM_DF = new SimpleDateFormat("yyyyMMdd");
    private static final SimpleDateFormat STR_DF = new SimpleDateFormat("yyyy-MM-dd");
    private static final SimpleDateFormat DF_DATETIME_NUM = new SimpleDateFormat("yyyyMMddHHmmss");
    @Autowired
    private ArchReviewRotaConfDao rotaConfDao;
    @Autowired
    private ArchReviewRotaDetailConfDao rotaDetailConfDao;
    @Autowired
    private IUserApiSvc userApiSvc;
    @Autowired
    private ESUserSvc userSvc;
    @Autowired
    private RedisUtil redisUtil;
    @Value("${http.resource.space}")
    private String httpPath;

    @Override
    public void save(ArchReviewRotaConfSaveReq req) {
        log.info("保存值班表信息:{}", JSON.toJSONString(req));
        ArchReviewRotaContext.init();
        //校验入参
        checkSaveParams(req);
        //维护值班表
        checkAndInitArchReviewRotaConf(req);
        //维护值班表明细
        checkAndInitArchReviewRotaDetailConf(req);
        //入库
        saveRota();
        ArchReviewRotaContext.release();
    }

    @Override
    public void checkPre(ArchReviewRotaConfSaveReq req) {
        log.info("校验值班表信息:{}", JSON.toJSONString(req));
        ArchReviewRotaContext.init();
        //校验入参
        checkSaveParams(req);
        //维护值班表
        checkAndInitArchReviewRotaConf(req);
        //维护值班表明细
        checkAndInitArchReviewRotaDetailConf(req);
        ArchReviewRotaContext.release();
    }

    /**
     * 校验保存入参
     * @param req
     */
    private void checkSaveParams(ArchReviewRotaConfSaveReq req) {
        Assert.notNull(req, "值班表名称不能为空");
        Assert.notNull(req.getName(), "值班表名称不能为空");
        Assert.notNull(req.getStartType(), "启用方式不能为空");
        if (RoteStartType.ON_DATE.equals(req.getStartType())) {
            Assert.notNull(req.getStartDate(), "启用日期不能为空");
            Date startDate = getDate(req.getStartDate());
            if (startDate.before(new Date())) {
                if (req.getId() == null) {
                    throw new BinaryException("启用日期须在当前日期之后");
                }
                ArchReviewRotaConf rotaConf = rotaConfDao.getById(req.getId());
                Assert.notNull(rotaConf, "未找到值班表配置");
                switch (rotaConf.getState()) {
                    case PRE:
                        throw new BinaryException("启用日期须在当前日期之后");
                    case USING:
                        break;
                    case HISTORY:
                        throw new BinaryException("历史值班表不允许修改");
                }
            }
            Long startDateLong = ESUtil.getNumberDateTime(startDate);
            List<ArchReviewRotaConf> sameStartDateCounts = rotaConfDao.getListByQuery(QueryBuilders.termQuery("startDate", startDateLong));
            if (sameStartDateCounts.size() > 0) {
                Long existId = sameStartDateCounts.get(0).getId();
                if (req.getId() == null || !existId.equals(req.getId())) {
                    throw new BinaryException("已存在当前启用日期的值班表，请重新设置启用日期");
                }
            }
        }
        Assert.notNull(req.getDirectors(), "评审负责人不能为空");
        Set<String> directors = new HashSet<>(req.getDirectors());
        List<SysUser> users = getUserByLoginCodes(directors);
        if (CollectionUtils.isEmpty(users) || directors.size() != users.size()) {
            throw new BinaryException("部分评审负责人不存在，请刷新列表重试");
        }
        if (CollectionUtils.isEmpty(req.getDetails())) {
            throw new BinaryException("值班表明细不能为空");
        }
        List<ArchReviewRotaConfSaveReq.Detail> sortDetails = EamUtil.copy(req.getDetails(), ArchReviewRotaConfSaveReq.Detail.class);
        Collections.sort(sortDetails, (o1, o2) -> {
            Assert.notNull(o1.getStartDate(), "开始时间不能为空");
            Assert.notNull(o2.getStartDate(), "开始时间不能为空");
            Date o1Std = getDate(o1.getStartDate());
            Date o2Std = getDate(o2.getStartDate());
            if (o1Std.after(o2Std)) {
                return 1;
            } else if (o1Std.before(o2Std)) {
                return -1;
            }
            return 0;
        });
        Set<String> directorLoginCodes = new HashSet<>();
        Integer[][] intervals = new Integer[sortDetails.size()][2];
        for (int i = 0; i < sortDetails.size(); i++) {
            ArchReviewRotaConfSaveReq.Detail detail = sortDetails.get(i);
            Assert.notNull(detail.getStartDate(), "开始时间不能为空");
            Assert.notNull(detail.getEndDate(), "结束时间不能为空");
            Assert.notNull(detail.getDirectors(), "负责人不能为空");
            directorLoginCodes.addAll(detail.getDirectors());
            //开始时间小于结束时间
            Date curStartDate = getDate(detail.getStartDate());
            Date curEndDate = getDate(detail.getEndDate());
            if (curStartDate.after(curEndDate)) {
                throw new BinaryException("开始时间须小于结束时间");
            }
            int numCurStartDate = Integer.parseInt(NUM_DF.format(curStartDate));
            int numCurEndDate = Integer.parseInt(NUM_DF.format(curEndDate));
            intervals[i][0] = numCurStartDate;
            intervals[i][1] = numCurEndDate;
            if (i == 0) {
                continue;
            }
            ArchReviewRotaConfSaveReq.Detail preDetail = sortDetails.get(i - 1);
            Date preEndDate = getDate(preDetail.getEndDate());

            long msNum = curStartDate.getTime() - preEndDate.getTime();
            long dayNum = msNum/(24*60*60*1000);
            if (dayNum == 1) {
                continue;
            }
            if (dayNum > 1) {
                String rangeMin = getPreOrNextOneDayStr(preEndDate, false);
                String rangeMax = getPreOrNextOneDayStr(curStartDate, true);
                String msg = rangeMin.equals(rangeMax) ?
                        rangeMax.concat("未配置值班人员信息，请补充") :
                        rangeMin.concat("至").concat(rangeMax).concat("，未配置值班人员信息，请补充");
                throw new BinaryException(msg);
            }
            throw new BinaryException("值班人配置时间段重叠，请核查");
        }
        if (eraseOverlapIntervals(intervals) != 0) {
            throw new BinaryException("值班人配置时间段重叠，请核查");
        }
        List<SysUser> sysUsers = getUserByLoginCodes(directorLoginCodes);
        Map<String, SimpUserInfo> directorMap = sysUsers.stream().collect(
                Collectors.toMap(SysUser::getLoginCode, e -> new SimpUserInfo(e.getLoginCode(), e.getUserName())));
        ArchReviewRotaContextValue context = ArchReviewRotaContext.getContext();
        context.setDirectorMap(directorMap);
        ArchReviewRotaContext.setContext(context);
    }

    private Date getDate(String dateStr) {
        try {
            return STR_DF.parse(dateStr);
        } catch (ParseException e) {
            throw new BinaryException("日期转换错误:" + dateStr);
        }
    }

    /**
     * 获取指定日期的前一天/后一天的指定格式字符串
     * @param date
     * @param pre
     * @return
     */
    private String getPreOrNextOneDayStr(Date date, Boolean pre) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(date.getTime());
        int amount = pre ? -1 : 1;
        calendar.add(Calendar.DATE, amount);
        return STR_DF.format(calendar.getTime());
    }

    /**
     * 校验区间是否重叠
     * @param intervals
     * @return
     */
    private int eraseOverlapIntervals(Integer[][] intervals) {
        if (intervals.length == 0) {
            return 0;
        }
        Arrays.sort(intervals, (interval1, interval2) -> {
            double value = interval1[1] - interval2[1];
            return value < 0 ? -1 : value > 0 ? 1 : 0;
        });
        int n = intervals.length;
        double right = intervals[0][1];
        int ans = 1;
        for (int i = 1; i < n; ++i) {
            if (intervals[i][0] >= right) {
                ++ans;
                right = intervals[i][1];
            }
        }
        return n - ans;
    }

    private List<SysUser> getUserByLoginCodes(Set<String> loginCodes) {
        CSysUser cdt = new CSysUser();
        cdt.setLoginCodes(loginCodes.toArray(new String[loginCodes.size()]));
        return userApiSvc.getSysUserByCdt(cdt);
    }

    private void checkAndInitArchReviewRotaConf(ArchReviewRotaConfSaveReq req) {
        ArchReviewRotaConf saveRotaConf = new ArchReviewRotaConf();
        String curUserLoginCode = SysUtil.getCurrentUserInfo().getLoginCode();
        if (req.getId() != null) {
            ArchReviewRotaConf rotaConf = rotaConfDao.getById(req.getId());
            Assert.notNull(rotaConf, "未找到值班表配置");
            if (RotaState.HISTORY.equals(rotaConf.getState())) {
                throw new BinaryException("历史值班表不允许修改");
            }
            saveRotaConf = EamUtil.copy(rotaConf, ArchReviewRotaConf.class);
        } else {
            saveRotaConf.setId(ESUtil.getUUID());
            saveRotaConf.setState(RotaState.PRE);
            saveRotaConf.setCreatorCode(curUserLoginCode);
            saveRotaConf.setCreateTime(ESUtil.getNumberDateTime());
        }
        //值班表同名校验
        List<ArchReviewRotaConf> sameNames = rotaConfDao.getListByQuery(QueryBuilders.termQuery("name.keyword", req.getName()));
        if (!CollectionUtils.isEmpty(sameNames)) {
            for (ArchReviewRotaConf sameName : sameNames) {
                if (req.getId() == null || !req.getId().equals(sameName.getId())) {
                    throw new BinaryException("值班表名称不可重复");
                }
            }
        }
        saveRotaConf.setName(req.getName());
        //仅预设值班表可修改启用方式/启用时间
        if (RotaState.PRE.equals(saveRotaConf.getState())) {
            saveRotaConf.setStartType(req.getStartType());
            saveRotaConf.setStartDate(StringUtils.isBlank(req.getStartDate()) ?
                    null : ESUtil.getNumberDateTime(getDate(req.getStartDate())));
        }
        List<SysUser> users = getUserByLoginCodes(new HashSet<>(req.getDirectors()));
        saveRotaConf.setDirectors(req.getDirectors());
        saveRotaConf.setDirectorInfos(EamUtil.copy(users, SimpUserInfo.class));
        saveRotaConf.setModifierCode(curUserLoginCode);
        saveRotaConf.setModifyTime(ESUtil.getNumberDateTime());
        List<ArchReviewRotaConf> updates = new ArrayList<>();
        //立即启用
        if (RoteStartType.IMMEDIATELY.equals(req.getStartType()) && RotaState.PRE.equals(saveRotaConf.getState())) {
            //获取在用值班配置表
            BoolQueryBuilder query = QueryBuilders.boolQuery();
            query.must(QueryBuilders.termQuery("state.keyword", RotaState.USING));
            Page<ArchReviewRotaConf> page = rotaConfDao.getSortListByQuery(1, 1, query, "actuallyStartTime", false);
            if (!CollectionUtils.isEmpty(page.getData())) {
                ArchReviewRotaConf using = page.getData().get(0);
                //非当前值班配置表
                if (!using.getId().equals(saveRotaConf.getId())) {
                    //在用更新为历史版本
                    using.setState(RotaState.HISTORY);
                    using.setActuallyStopTime(ESUtil.getNumberDateTime());
                    updates.add(using);
                }
            }
            if (RotaState.PRE.equals(saveRotaConf.getState())) {
                saveRotaConf.setActuallyStartTime(ESUtil.getNumberDateTime());
            }
            //待启用更新为在用
            saveRotaConf.setState(RotaState.USING);
        }
        updates.add(saveRotaConf);
        ArchReviewRotaContextValue context = ArchReviewRotaContext.getContext();
        context.setRotaConfId(saveRotaConf.getId());
        context.setRotaConfs(updates);
        ArchReviewRotaContext.setContext(context);
    }

    private void checkAndInitArchReviewRotaDetailConf(ArchReviewRotaConfSaveReq req) {
        ArchReviewRotaContextValue context = ArchReviewRotaContext.getContext();
        Long rotaConfId = context.getRotaConfId();
        Map<String, SimpUserInfo> dbDirectorMap = context.getDirectorMap();
        Map<String, SimpUserInfo> redundanceDirectorMap = new ConcurrentHashMap<>();
        List<ArchReviewRotaDetailConf> dbRotaDetailConfs = rotaDetailConfDao.getListByQuery(QueryBuilders.termQuery("rotaConfId", rotaConfId));
        if (!CollectionUtils.isEmpty(dbRotaDetailConfs)) {
            List<SimpUserInfo> redundanceDirectors = new ArrayList<>();
            for (ArchReviewRotaDetailConf dbRotaDetailConf : dbRotaDetailConfs) {
                redundanceDirectors.addAll(dbRotaDetailConf.getDirectorInfos());
            }
            redundanceDirectorMap = redundanceDirectors.stream().collect(Collectors.toMap(SimpUserInfo::getLoginCode, e -> e, (k1,k2) -> k1));
        }
        List<ArchReviewRotaDetailConf> confs = new ArrayList<>();
        for (ArchReviewRotaConfSaveReq.Detail detail : req.getDetails()) {
            ArchReviewRotaDetailConf conf = new ArchReviewRotaDetailConf();
            conf.setRotaConfId(rotaConfId);
            //日期传参只取年月日
            conf.setStartDate(ESUtil.getNumberDateTime(getDate(detail.getStartDate())));
            conf.setEndDate(ESUtil.getNumberDateTime(getDate(detail.getEndDate())));
            conf.setDirectors(detail.getDirectors());
            List<SimpUserInfo> directorInfos = new ArrayList<>();
            for (String director : detail.getDirectors()) {
                String userName = dbDirectorMap.containsKey(director) ?
                        dbDirectorMap.get(director).getUserName() :
                        redundanceDirectorMap.containsKey(director) ?
                                redundanceDirectorMap.get(director).getUserName() : director;
                directorInfos.add(new SimpUserInfo(director, userName));
            }
            conf.setDirectorInfos(directorInfos);
            conf.setNotes(detail.getNotes());
            confs.add(conf);
        }
        context.setRotaDetailConfs(confs);
        ArchReviewRotaContext.setContext(context);
    }

    private void saveRota() {
        ArchReviewRotaContextValue context = ArchReviewRotaContext.getContext();
        Long rotaConfId = context.getRotaConfId();
        //维护值班表
        rotaConfDao.deleteById(rotaConfId);
        rotaConfDao.saveOrUpdateBatch(context.getRotaConfs());
        //维护值班表明细
        deleteRotaDetailConfByRotaId(rotaConfId);
        rotaDetailConfDao.saveOrUpdateBatch(context.getRotaDetailConfs());
    }

    private void deleteRotaDetailConfByRotaId(Long rotaConfId) {
        TermQueryBuilder query = QueryBuilders.termQuery("rotaConfId", rotaConfId);
        rotaDetailConfDao.deleteByQuery(query, true);
    }

    @Override
    public Page<ArchReviewRotaConfQueryRes> list(ArchReviewRotaConfQueryReq req) {
        Assert.notNull(req, "分页参数不能为空");
        Assert.notNull(req.getPageNum(), "第几页不能为空");
        Assert.notNull(req.getPageSize(), "每页显示记录数不能为空");
        Assert.notNull(req.getState(), "值班表状态不能为空");
        //值班表
        BoolQueryBuilder rotaQuery = QueryBuilders.boolQuery();
        rotaQuery.must(QueryBuilders.termQuery("state.keyword", req.getState()));
        List<SortBuilder<?>> sorts = new ArrayList<>();
        switch (req.getState()) {
            case PRE:
                //预设值班表：启用时间升序
                FieldSortBuilder startTimeSort = SortBuilders.fieldSort("startDate").order(SortOrder.ASC);
                sorts.add(startTimeSort);
                break;
            case USING:
                //在用值班表：最多只有一个，默认创建时间降序
                FieldSortBuilder createTimeSort = SortBuilders.fieldSort("createTime").order(SortOrder.DESC);
                sorts.add(createTimeSort);
                break;
            case HISTORY:
                //历史值班表：停用时间降序
                FieldSortBuilder stopTimeSort = SortBuilders.fieldSort("actuallyStopTime").order(SortOrder.DESC);
                sorts.add(stopTimeSort);
                break;
            default:
                throw new BinaryException("不支持的值班表状态");
        }
        Page<ArchReviewRotaConf> page = rotaConfDao.getSortListByQuery(req.getPageNum(), req.getPageSize(), rotaQuery, sorts);
        if (CollectionUtils.isEmpty(page.getData())) {
            return new Page<>(page.getPageNum(), page.getPageSize(), 0, 0, new ArrayList<>());
        }
        List<ArchReviewRotaConf> rotaConfs = page.getData();
        Set<String> loginCodes = new HashSet<>();
        List<SimpUserInfo> userInfos = new ArrayList<>();
        rotaConfs.forEach(rotaConf -> {
            loginCodes.addAll(rotaConf.getDirectors());
            userInfos.addAll(rotaConf.getDirectorInfos());
        });
        List<Long> rotaConfIds = rotaConfs.stream().map(ArchReviewRotaConf::getId).collect(Collectors.toList());
        List<ArchReviewRotaDetailConf> rotaDetailConfs = getByRotaConfIds(rotaConfIds);
        rotaDetailConfs.forEach(rotaDetailConf -> {
            loginCodes.addAll(rotaDetailConf.getDirectors());
            userInfos.addAll(rotaDetailConf.getDirectorInfos());
        });
        rotaDetailConfs.sort(Comparator.comparing(ArchReviewRotaDetailConf::getRotaConfId).thenComparing(ArchReviewRotaDetailConf::getId));
        Map<Long, List<ArchReviewRotaDetailConf>> groupByRotaConfId = rotaDetailConfs.stream()
                .collect(Collectors.groupingBy(ArchReviewRotaDetailConf::getRotaConfId));

        List<SysUser> dbUsers = userSvc.getListByQueryScroll(QueryBuilders.termsQuery("loginCode.keyword", loginCodes));
        Map<String, SimpUserInfo> dbUserInfo = EamUtil.copy(dbUsers, SimpUserInfo.class)
                .stream().collect(Collectors.toMap(SimpUserInfo::getLoginCode, e -> e));
        Map<String, SimpUserInfo> redundanceUserMap = userInfos.stream()
                .collect(Collectors.toMap(SimpUserInfo::getLoginCode, e -> e, (k1,k2) -> k1));
        List<ArchReviewRotaConfQueryRes> results = new ArrayList<>();
        for (ArchReviewRotaConf rotaConf : rotaConfs) {
            ArchReviewRotaConfQueryRes result = new ArchReviewRotaConfQueryRes();
            result.setId(rotaConf.getId());
            result.setName(rotaConf.getName());
            result.setState(rotaConf.getState());
            result.setStartType(rotaConf.getStartType());
            if (rotaConf.getStartDate() != null) {
                try {
                    result.setStartDate(STR_DF.format(DF_DATETIME_NUM.parse(rotaConf.getStartDate().toString())));
                } catch (ParseException e) {
                    log.error("时间转换失败：{}", rotaConf.getStartDate().toString());
                }
            }
            if (rotaConf.getActuallyStopTime() != null) {
                try {
                    result.setActuallyStopTime(STR_DF.format(DF_DATETIME_NUM.parse(rotaConf.getActuallyStopTime().toString())));
                } catch (ParseException e) {
                    log.error("时间转换失败：{}", rotaConf.getActuallyStopTime().toString());
                }
            }
            result.setDirectors(rotaConf.getDirectors());
            List<SimpUserInfo> directorInfos = new ArrayList<>();
            for (String director : rotaConf.getDirectors()) {
                directorInfos.add(dbUserInfo.getOrDefault(director, redundanceUserMap.get(director)));
            }
            result.setDirectorInfos(directorInfos);
            List<ArchReviewRotaDetailConf> rotaDConfs = groupByRotaConfId.get(rotaConf.getId());
            List<ArchReviewRotaConfQueryRes.Detail> resultRotaDConfs = new ArrayList<>();
            for (ArchReviewRotaDetailConf rotaDetailConf : rotaDConfs) {
                ArchReviewRotaConfQueryRes.Detail rotaDConf = new ArchReviewRotaConfQueryRes.Detail();
                try {
                    rotaDConf.setStartDate(STR_DF.format(DF_DATETIME_NUM.parse(rotaDetailConf.getStartDate().toString())));
                    rotaDConf.setEndDate(STR_DF.format(DF_DATETIME_NUM.parse(rotaDetailConf.getEndDate().toString())));
                } catch (ParseException e) {
                    log.error("时间转换失败：startDate:{},endDate:{}",
                            rotaDetailConf.getStartDate().toString(), rotaDetailConf.getEndDate().toString());
                }
                rotaDConf.setDirectors(rotaDetailConf.getDirectors());
                List<SimpUserInfo> rotaDDirectorInfos = new ArrayList<>();
                for (String director : rotaDetailConf.getDirectors()) {
                    rotaDDirectorInfos.add(dbUserInfo.getOrDefault(director, redundanceUserMap.get(director)));
                }
                rotaDConf.setDirectorInfos(rotaDDirectorInfos);
                rotaDConf.setNotes(rotaDetailConf.getNotes());
                resultRotaDConfs.add(rotaDConf);
            }
            result.setDetails(resultRotaDConfs);
            results.add(result);
        }
        return new Page<>(req.getPageNum(), req.getPageSize(), page.getTotalRows(), page.getTotalPages(), results);
    }

    private List<ArchReviewRotaDetailConf> getByRotaConfIds(List<Long> rotaConfIds) {
        TermsQueryBuilder query = QueryBuilders.termsQuery("rotaConfId", rotaConfIds);
        return rotaDetailConfDao.getListByQueryScroll(query);
    }

    @Override
    public Page<ArchReviewRotaDirectorInfo> rotaDirectorInfo(ArchReviewRotaDirectorInfoReq req) {
        Assert.notNull(req, "分页参数不能为空");
        Assert.notNull(req.getPageNum(), "第几页不能为空");
        Assert.notNull(req.getPageSize(), "每页显示记录数不能为空");
        BoolQueryBuilder rotaQuery = QueryBuilders.boolQuery();
        if (StringUtils.isNotBlank(req.getKeyword())) {
            rotaQuery.must(QueryBuilders.wildcardQuery("name.keyword", "*" + req.getKeyword() + "*"));
        }
        List<SortBuilder<?>> sorts = new ArrayList<>();
        FieldSortBuilder createTimeSort = SortBuilders.fieldSort("createTime").order(SortOrder.DESC);
        sorts.add(createTimeSort);
        Page<ArchReviewRotaConf> page = rotaConfDao.getSortListByQuery(req.getPageNum(), req.getPageSize(), rotaQuery, sorts);
        if (CollectionUtils.isEmpty(page.getData())) {
            return new Page<>(page.getPageNum(), page.getPageSize(), 0, 0, new ArrayList<>());
        }
        List<ArchReviewRotaConf> rotaConfs = page.getData();
        Set<String> loginCodes = new HashSet<>();
        List<SimpUserInfo> userInfos = new ArrayList<>();
        rotaConfs.forEach(rotaConf -> {
            loginCodes.addAll(rotaConf.getDirectors());
            userInfos.addAll(rotaConf.getDirectorInfos());
        });
        List<SysUser> dbUsers = userSvc.getListByQueryScroll(QueryBuilders.termsQuery("loginCode.keyword", loginCodes));
        Map<String, SimpUserInfo> dbUserInfo = EamUtil.copy(dbUsers, SimpUserInfo.class)
                .stream().collect(Collectors.toMap(SimpUserInfo::getLoginCode, e -> e));
        Map<String, SimpUserInfo> redundanceUserMap = userInfos.stream()
                .collect(Collectors.toMap(SimpUserInfo::getLoginCode, e -> e, (k1,k2) -> k1));
        List<ArchReviewRotaDirectorInfo> results = new ArrayList<>();
        for (ArchReviewRotaConf rotaConf : rotaConfs) {
            ArchReviewRotaDirectorInfo result = new ArchReviewRotaDirectorInfo();
            result.setId(rotaConf.getId());
            result.setName(rotaConf.getName());
            result.setDirectors(rotaConf.getDirectors());
            List<SimpUserInfo> directorInfos = new ArrayList<>();
            for (String director : rotaConf.getDirectors()) {
                directorInfos.add(dbUserInfo.getOrDefault(director, redundanceUserMap.get(director)));
            }
            result.setDirectorInfos(directorInfos);
            results.add(result);
        }
        return new Page<>(req.getPageNum(), req.getPageSize(), page.getTotalRows(), page.getTotalPages(), results);
    }

    @Override
    public void startRotaConfJob() {
        String key = "lock:start:rota:conf:" + httpPath.replace(":", "");
        boolean lock = redisUtil.setnx(key, "1", 60, TimeUnit.SECONDS);
        if (!lock) {
            log.info("获取锁失败");
            return;
        }
        try {
            doStartRotaConfJob();
        } catch (Exception e) {
            log.error("启用值班表配置失败:{}",JSON.toJSONString(e));
        }
        redisUtil.del(key);
    }

    @Async
    private void doStartRotaConfJob() {
        BoolQueryBuilder rotaQuery = QueryBuilders.boolQuery();
        //启用方式为到达指定日期
        rotaQuery.must(QueryBuilders.termQuery("startType.keyword", RoteStartType.ON_DATE));
        //状态为预设
        rotaQuery.must(QueryBuilders.termQuery("state.keyword", RotaState.PRE));
        //启用日期是今天
        String startDate = NUM_DF.format(new Date()) + "000000";
        rotaQuery.must(QueryBuilders.termQuery("startDate", Long.valueOf(startDate)));
        List<SortBuilder<?>> sorts = new ArrayList<>();
        FieldSortBuilder createTimeSort = SortBuilders.fieldSort("createTime").order(SortOrder.DESC);
        sorts.add(createTimeSort);
        Page<ArchReviewRotaConf> page = rotaConfDao.getSortListByQuery(1, 1, rotaQuery, sorts);
        if (!CollectionUtils.isEmpty(page.getData())) {
            ArchReviewRotaConf rotaConf = page.getData().get(0);
            //获取在用值班配置表
            BoolQueryBuilder usingQuery = QueryBuilders.boolQuery();
            usingQuery.must(QueryBuilders.termQuery("state.keyword", RotaState.USING));
            Page<ArchReviewRotaConf> usingPage = rotaConfDao.getSortListByQuery(1, 1, usingQuery, "actuallyStartTime", false);
            List<ArchReviewRotaConf> updates = new ArrayList<>();
            if (!CollectionUtils.isEmpty(usingPage.getData())) {
                ArchReviewRotaConf using = usingPage.getData().get(0);
                //在用更新为历史版本
                using.setState(RotaState.HISTORY);
                using.setActuallyStopTime(ESUtil.getNumberDateTime());
                updates.add(using);
            };
            //待启用更新为在用
            rotaConf.setState(RotaState.USING);
            rotaConf.setActuallyStartTime(ESUtil.getNumberDateTime());
            updates.add(rotaConf);
            rotaConfDao.saveOrUpdateBatch(updates);
        }
    }

    @Override
    public List<String> getDirectorsOnDate(Date date) {
        String dateStr = NUM_DF.format(date) + "000000";
        Long dateLong = Long.valueOf(dateStr);
        log.info("dateLong:{}", dateLong);
        //在用配置
        BoolQueryBuilder rotaQuery = QueryBuilders.boolQuery();
        rotaQuery.must(QueryBuilders.termQuery("state.keyword", RotaState.USING));
        List<ArchReviewRotaConf> rotaConfs = rotaConfDao.getListByQuery(rotaQuery);
        if (CollectionUtils.isEmpty(rotaConfs)) {
            log.error("暂无在用值班表配置");
            return new ArrayList<>();
        }
        ArchReviewRotaConf rotaConf = rotaConfs.get(0);
        TermQueryBuilder rotaDetailQuery = QueryBuilders.termQuery("rotaConfId", rotaConf.getId());
        List<ArchReviewRotaDetailConf> archReviewRotaDetailConfs = rotaDetailConfDao.getListByQuery(rotaDetailQuery);
        if (CollectionUtils.isEmpty(archReviewRotaDetailConfs)) {
            log.error("暂无值班表明细[rotaConfId:{}]", rotaConf.getId());
            return new ArrayList<>();
        }
        log.info("在用值班表明细:{}", JSON.toJSONString(archReviewRotaDetailConfs));
        for (ArchReviewRotaDetailConf rotaDetailConf : archReviewRotaDetailConfs) {
            if (dateLong >= rotaDetailConf.getStartDate() && dateLong <= rotaDetailConf.getEndDate()) {
                return rotaDetailConf.getDirectors();
            }
        }
        log.error("{}无对应值班日期/值班人", dateLong);
        return null;
    }
}
